<?php

/***  Documentation

    Greqo Analytics Class
    Last Update: $Date: 2009-09-24 03:43:42 +0000 (Thu, 24 Sep 2009) $
    Author: Tom at klenwell@gmail.com

DESCRIPTION
    Programmatic interface for Google Analytics service
  
USAGE


NOTES
    This class is designed to be compatible with PHP 4.  Disable strict error
    notices in PHP 5 or above.
    
    SESSION values
        $_SESSION['GDATA']['auth_token']
    
______________________________________________________________________________*/

// load base Greqo class
require_once('greqo.class.php');

class GreqoAnalytics extends Greqo
{
    // service settings
    var $service = 'analytics';
    var $gdata_version = '1.0';
    
    var $LoginCookies = array();
    var $REPORTS = array();
    var $is_logged_in = 0;
    var $site_id = 0;
    
    // Google Analytic Urls
    var $URL = array(
        'google'        => 'https://www.google.com/',
        'home'          => 'analytics/home/',
        'login'         => 'accounts/ServiceLoginBox?service=analytics&nui=1&hl=en-US&continue=http://www.google.com/analytics/home/%3Fet%3Dreset%26hl%3Den-US',
        'login_auth'    => 'accounts/ServiceLoginBoxAuth',
        'login_cont'    => 'analytics/home/?et=reset&amp;hl=en-US',
        'report'        => 'analytics/reporting/export?',
        'account'       => 'analytics/settings/?ns=100&',
        'profile'       => 'analytics/settings/home?ns=100&scid=%s'
    );
    var $query_str      = '&user=&vid=2001&ns=10&ss=0&fd=&ft=2&sf=2&sb=1&dow=0&dt=3&dtc=2&xd=1';
    var $login_key      = 'GA3T';
    
    // Google Analytic Constants
    var $GA_FMT_XML     = 1;
    var $GA_FMT_CSV     = 2;
    var $GA_CMP_AVG     = 'average';        // default cmp value
    var $GA_CMP_DATE    = 'date_range';     // compare by date range
    
    var $GaReportTypes  = array(
        'traffic' => 'VisitorsOverviewReport',
        'sources' => 'AllSourcesReport',
        'keywords' => 'KeywordsReport',
        'content' => 'TopContentReport'        
    );
    
    // other constants
    var $day_sec = 86400;
    var $week_sec = 604800;


    /* MAGIC METHODS */
    // php4 constructor
    function GreqoAnalytics($start_session=1)
    {
        $this->__construct($start_session);
        register_shutdown_function( array($this, '__destruct') );
    }


    // php5 constructor
    function __construct($ss)
    {
        // load parent class
        parent::__construct($ss);
        
        // additional settings
        $this->load_cacher();
    }
    
    
    // destructor
    function __destruct()
    {
        $this->debug(sprintf("%s END\n\n", __CLASS__));
    }
  

    /* ** PUBLIC METHODS ** */
    function get_summary_report($site_id, $start_date=null, $end_date=null)
    {
        $Report = $this->get_all_reports($site_id, $start_date, $end_date);
        return $this->build_summary_report($Report);
    }
    
    function get_all_reports($site_id, $start_date=null, $end_date=null)
    {
        $Report = array();
        foreach ( $this->GaReportTypes as $id => $type )
        {
            $get_ = sprintf('get_%s_report', $id);
            $parse_ = ( $id == 'traffic' ) ? '_parse_traffic_xml' : '_parse_ga_csv';
            $source = $this->$get_($site_id, $start_date, $end_date);
            $Report[$id] = $this->$parse_($source);
        }
        return $Report;
    }
    
    function build_summary_report($ReportData)
    {
        $template = <<<HSTR
Greqo Analytics Summary for %s
Date Range: %s 

# ----------------------------------------
# TRAFFIC
Type : Current | Previous | Change
Visitors: %s | %s | %s
Visits: %s | %s | %s
Page Views: %s | %s | %s

# ----------------------------------------
# SOURCES
%s

# ----------------------------------------
# KEYWORDS
%s

# ----------------------------------------
# CONTENT
%s

HSTR;

        $Visitors = $ReportData['traffic']['VisitorsSummary'];
        $Visits = $ReportData['traffic']['VisitsSummary'];
        $PageViews = $ReportData['traffic']['PageviewsSummary'];
        
        // set date range
        $DateSplit = explode('-', $ReportData['traffic']['DateRange']);
        $date_range = sprintf('%s to %s',
            date('Y-m-d', strtotime($DateSplit[0])),
            date('Y-m-d', strtotime($DateSplit[1])));
        
        $DataLines = array();
        foreach ( $this->GaReportTypes as $id => $type )
        {
            if ( $id == 'traffic' ) continue;
            $DataLines = array();

            // report table header
            $HeadSlice = array_slice($ReportData[$id]['Table']['Header'], 0, 5);
            $DataLines[] = sprintf('%s : %s',
                              array_shift($HeadSlice),
                              implode(' | ', $HeadSlice));
                
            // report table rows
            foreach ( $ReportData[$id]['Table']['Rows'] as $RowData )
            {
                $RowSlice = array_slice($RowData, 0, 5);
                $DataLines[] = sprintf('%s : %s',
                              array_shift($RowSlice),
                              implode(' | ', $RowSlice));
            }
            
            $SerialReport[$id] = implode("\n", $DataLines);
        }
                
        return sprintf( $template,
            $ReportData['traffic']['ProfileName'],
            $date_range,
            $Visitors['value'], $Visitors['previous'], $Visitors['change'],
            $Visits['value'], $Visits['previous'], $Visits['change'],
            $PageViews['value'], $PageViews['previous'], $PageViews['change'],
            $SerialReport['sources'],
            $SerialReport['keywords'],
            $SerialReport['content'] );
    }
    
    function parse_summary_report($report)
    {
        /* parse a summary report string into an array */
        $ReportData= array();
        $sep = '# ----------------------------------------';
        
        // split report head and data
        $SectionList = explode($sep, trim($report));
        $head = $SectionList[0];
        $SubreportList = array_slice($SectionList, 1);
        
        // parse head
        $HeadData = array();
        $Lines = explode("\n", trim($head));
        $HeadTitle = explode('for', $Lines[0]);
        $HeadDates = $this->explode_ga_line($Lines[1]);
        $HeadDateRange = explode('to', $HeadDates[1]);
        $HeadData['profile'] = trim($HeadTitle[1]);
        $HeadData['start_date'] = trim($HeadDateRange[0]);
        $HeadData['end_date'] = trim($HeadDateRange[1]);
        
        // parse data        
        foreach ( $SubreportList as $section )
        {
            $Lines = explode("\n", trim($section));
            $title = trim(str_replace('#', '', $Lines[0]));
            $cols = $Lines[1];
            $ValueLines = array_slice($Lines, 2);
            
            $ColList = $this->explode_ga_line($Lines[1]);
            
            $SubreportData[$title] = array();
            foreach ( $ValueLines as $line )
            {
                $LineData = array();
                $ValueList = $this->explode_ga_line($line);
                foreach ( range(0,count($ValueList)-1) as $i )
                    $LineData[trim($ColList[$i])] = trim($ValueList[$i]);
                $SubreportData[$title][trim($ValueList[0])] = $LineData;
            }
        }
        
        $ReportData = array_merge($HeadData, $SubreportData);
        return $ReportData;
    }
    
    function get_report_extract($report_url, $force_reload=0)
    {
        // login check
        if ( ! $this->is_logged_in )
            return $this->err('you must log in before getting report extract');
        
        // check for cache
        $cache_id = $this->_get_cache_id($report_url);
        if ( !$force_reload )
        {
            if ( $report_data = $this->Cacher->get($cache_id) )
            {
                $this->debug( sprintf(
                    'found cache %s for report %s', $cache_id, $report_url ));
                return $report_data;
            }
        }
        
        // prepare and send request
        $this->GreqoClient->setMethod(HTTP_REQUEST_METHOD_GET);
        $this->GreqoClient->setURL($report_url);
        $this->_send_client_request();

        // test response
        if ( $this->GreqoResponse->code_class != 2 )
            return $this->err( sprintf ( 'failed to get report %s: code %s',
                $report_url, $this->GreqoResponse->code ));
       
        // get data
        $report_data = $this->GreqoResponse->body;
        
        // cache report
        if ( $this->Cacher->save($report_data, $cache_id) )
            $this->debug( sprintf(
                'cached report %s [id: %s]', $report_url, $cache_id) );
        else
            $this->err(sprintf('unable to cache report %s', $report_url));
        
        return $report_data;        
    }
    
    function get_traffic_report($site_id, $start_date=null, $end_date=null)
    {
        $type  = 'traffic';
        $fmt   = $this->GA_FMT_XML;
        $cmp   = $this->GA_CMP_DATE;
        $trows = 10;
        
        if ( !$url = $this->_build_report_url($site_id, $type, $start_date,
                $end_date, $fmt, $cmp, $trows) )
            return $this->err('invalid report url');
        
        return $this->get_report_extract($url);
    }
    
    function get_sources_report($site_id, $start_date=null, $end_date=null)
    {
        $type  = 'sources';
        $fmt   = $this->GA_FMT_CSV;
        $cmp   = $this->GA_CMP_AVG;
        $trows = 10;
        
        if ( !$url = $this->_build_report_url($site_id, $type, $start_date,
                $end_date, $fmt, $cmp, $trows) )
            return $this->err('invalid report url');
        
        return $this->get_report_extract($url);
    }
    
    function get_keywords_report($site_id, $start_date=null, $end_date=null)
    {
        $type  = 'keywords';
        $fmt   = $this->GA_FMT_CSV;
        $cmp   = $this->GA_CMP_AVG;
        $trows = 10;
        
        if ( !$url = $this->_build_report_url($site_id, $type, $start_date,
                $end_date, $fmt, $cmp, $trows) )
            return $this->err('invalid report url');
        
        return $this->get_report_extract($url);
    }
    
    function get_content_report($site_id, $start_date=null, $end_date=null)
    {
        $type  = 'content';
        $fmt   = $this->GA_FMT_CSV;
        $cmp   = $this->GA_CMP_AVG;
        $trows = 10;
        
        if ( !$url = $this->_build_report_url($site_id, $type, $start_date,
                $end_date, $fmt, $cmp, $trows) )
            return $this->err('invalid report url');
        
        return $this->get_report_extract($url);
    }
    
    function login($force=0)
    {
        // already logged in?
        if ( $this->is_logged_in && !$force )
        {
            $this->log('already logged in and force (login) flag not set');
            return 1;
        }
        
        if ( !$AuthCookie =     $this->_login_s1() ) return 0;
        if ( !$tokenauth_url =  $this->_login_s2($AuthCookie) ) return 0;
        if ( !$location_url =   $this->_login_s3($tokenauth_url) ) return 0;
        $this->is_logged_in =   $this->_login_s4($location_url);
        
        return $this->is_logged_in;        
    }
    
    function logout()
    {
        $this->is_logged_in = 0;
        $this->reset_http_client();
    }
    
    function get_account_list()
    {
        $url = $this->absolute_url($this->URL['google'], $this->URL['account']);
        $method = HTTP_REQUEST_METHOD_GET;
        
        if ( !$this->is_logged_in ) $this->login();
        
        // prepare and send request
        $this->GreqoClient->setMethod($method);
        $this->GreqoClient->setURL($url);
        $this->_send_client_request();
        
        // test
        if ( ! $this->GreqoResponse->code_class == 2 )
            return $this->err(
                "failed to get account list: {$this->GreqoResponse->code_class}");
        
        // payload
        $AccountList = $this->_parse_account_list($this->GreqoResponse->body);
        return $AccountList;
    }
    
    function get_profile_list($account_id=null)
    {
        if ( is_null($account_id) )
            return $this->err('you must set the account id');
       
        $url = $this->absolute_url($this->URL['google'], $this->URL['profile']);
        $url = sprintf($url, $account_id);
        $method = HTTP_REQUEST_METHOD_GET;
        
        if ( !$this->is_logged_in ) $this->login();
        
        // prepare and send request
        $this->GreqoClient->setMethod($method);
        $this->GreqoClient->setURL($url);
        $this->_send_client_request();

        // test
        if ( ! $this->GreqoResponse->code_class == 2 )
            return $this->err(
                "failed to get profile list: {$this->GreqoResponse->code_class}");
        
        // payload
        $ProfileList = $this->_parse_profile_list($this->GreqoResponse->body);
        return $ProfileList;
    }
    
    
    // session methods
    
    
    // utility methods
    function normalize_report_date($datestr)
    {
        if ( !$uts = strtotime($datestr) )
            return $this->err("unable to normalize date: $datestr");
            
        return date('Ymd', $uts);
    }
    
    function uts_to_report_date($uts)
    {
        return $this->normalize_report_date(date('Y-m-d',$uts));
    }
    
    function explode_ga_line($line)
    {
        $List = array();
        $LineSplit = explode(':',$line);
        return array_merge( array($LineSplit[0]), explode('|', $LineSplit[1]) );
    }
    
    function err($m='')
    {
        parent::trigger_error($m, $type='analytics');
    }
    


    /* ** PRIVATE METHODS ** */
    function _parse_ga_csv($csv)
    {
        $Report = array(
            'ProfileName'   => '',
            'ReportName'    => '',
            'DateRange'     => '',
            'Table'         => array(
                'Header' => array(),
                'Rows'   => array()
            ),
        );
        
        $CsvSections = explode("\n\n", $csv);
        $HeadSection = $CsvSections[0];
        $TableSection = $CsvSections[2];
        
        // parse head section
        $HeadCsvLines = explode("\n", $HeadSection);
        
        foreach ( $HeadCsvLines as $ln_ )
            if ( !empty($ln_) && ( strpos($ln_, '# -') === false ) )
                $HeadData[] = $ln_;
        
        // Profile and report name
        $Report['ProfileName'] = $HeadData[0];
        $Report['ReportName'] = trim(str_replace(',', '', $HeadData[1]));
        
        // Dates
        $DateSplit = $this->explode_csv_line($HeadData[2]);
        foreach ( $DateSplit as $col ) if ( trim($col) != '' ) $DateRange[] = $col;
        $date0 = $this->normalize_report_date($DateRange[0]);
        $date1 = $this->normalize_report_date($DateRange[1]);
        $Report['DateRange'] = sprintf('%s-%s', $date0, $date1);
        
        // Table data
        $TableRows = array();
        $TableCsvLines = explode("\n", $TableSection);
        
        $trap_header = 1;
        foreach( $TableCsvLines as $ln_ )
        {
            $ln_ = trim($ln_);
            
            // skip comments and empty lines
            if ( empty($ln_) || $ln_[0] == '#' )
                continue;
            
            // trap header
            if ( $trap_header )
            {
                $trap_header = 0;
                $TableHeader = $this->explode_csv_line($ln_);
                continue;
            }
            
            // parse data
            $TableRow = array();
            $LineData = $this->explode_csv_line($ln_);
            foreach ( range(0,count($LineData)-1) as $n )
                $TableRow[$TableHeader[$n]] = $LineData[$n];

            $TableRows[] = $TableRow;
        }
        
        $Report['Table'] = array(
            'Header' => $TableHeader,
            'Rows'   => $TableRows
        );
        
        return $Report;
    }
    
    function _parse_traffic_xml($xml)
    {
        $Report = array();
        $SummaryTagList = array(
            'VisitorsSummary', 'VisitsSummary', 'PageviewsSummary' );
        
        if ( !$Document = $this->parse_xml($xml) ) return 0;
        
        // title
        $Report['ProfileName'] = $Document->report[0]->title[0]->profilename[0]->tagData;
        
        // get dates
        $dates = $Document->report[0]->title[0]->primarydaterange[0]->tagData;
        $DateRange = explode('-', $dates);
        $date0 = $this->normalize_report_date($DateRange[0]);
        $date1 = $this->normalize_report_date($DateRange[1]);
        $Report['DateRange'] = sprintf('%s-%s', $date0, $date1);
        
        // summary data
        foreach ( $Document->report[0]->itemsummary as $SummaryNode )
        {
            if ( in_array($SummaryNode->tagAttrs['id'], $SummaryTagList) )
            {
                // clean up previous
                $previous_ = strtolower($SummaryNode->submessage[0]->tagData);
                if ( strpos($previous_, ':') )
                {
                    $Plode = explode(':', $previous_);
                    $previous_ = trim($Plode[1]);
                }
                
                $Report[$SummaryNode->tagAttrs['id']] = array(
                    'value' => $SummaryNode->item[0]->summaryvalue[0]->tagData,
                    'previous' => $previous_,
                    'change' => $SummaryNode->delta[0]->tagData
                );
            }
        }
        
        return $Report;
    }
    
    function _build_report_url($site_id, $type, $start_date=null, $end_date=null, 
                             $fmt=null, $cmp=null, $trows=10)
    {
        $tpl_ = '%s%srpt=%s&id=%s&fmt=%s&pdr=%s-%s&cmp=%s%s&trows=%s';
        $uts_y = time() - $this->day_sec;
        
        if ( empty($site_id) || empty($type) )
            return $this->err('report url error: must set site id and report type');
        
        // type
        $ReportIdList = array_keys($this->GaReportTypes);
        if ( isset($this->GaReportTypes[$type]) ) $type = $this->GaReportTypes[$type];
        if ( !array_search($type, $this->GaReportTypes) )
            return $this->err('report url error: invalid report type');
        
        // format
        if ( is_null($fmt) ) $fmt = $this->GA_FMT_CSV;
        if ( !in_array($fmt, array($this->GA_FMT_CSV, $this->GA_FMT_XML)) )
            return $this->err('report url error: invalid report format');
        
        // report dates (default yesterday)
        if ( is_null($start_date) ) $start_date = date('Ymd', $uts_y);
        if ( is_null($end_date) ) $end_date = $start_date;
        if ( !$pdr1 = $this->normalize_report_date($start_date) )
            return 0;
        if ( !$pdr2 = $this->normalize_report_date($end_date) )
            return 0;
        if ( $pdr1 > $pdr2 )
        {
            $_td = $pdr1;
            $pdr1 = $pdr2;
            $pdr2 = $_td;
        }
        
        // compare settings
        if ( is_null($cmp) ) $cmp = $this->GA_CMP_DATE;
        if ( !in_array($cmp, array($this->GA_CMP_DATE, $this->GA_CMP_AVG)) )
            return $this->err('report url error: invalid cmp setting');
        $cdr = $this->_get_cdr($cmp, $pdr1, $pdr2);
        
        return sprintf( $tpl_, $this->URL['google'], $this->URL['report'], $type,
            $site_id, $fmt, $pdr1, $pdr2, $cmp, $cdr, $trows );
    }
    
    function _get_cdr($cmp, $pdr1, $pdr2)
    {
        $tpl_ = '&cdr=%s-%s';
        if ( $cmp == $this->GA_CMP_AVG ) return '';
        
        $uts1 = strtotime($pdr1);
        $uts2 = strtotime($pdr2);
        $span = $uts2 - $uts1;
        $cdr2 = $uts1 - $this->day_sec;
        $cdr1 = $cdr2 - $span;
        
        return sprintf( $tpl_, $this->uts_to_report_date($cdr1), $this->uts_to_report_date($cdr2) );
    }
    
    function _login_s1()
    {
        /*
            Login Step 1
            Get Auth Cookie
        */
        $url = $this->URL['google'].$this->URL['login'];
        $method = HTTP_REQUEST_METHOD_GET;
        
        $this->GreqoClient->setURL($url);
        $this->GreqoClient->setMethod($method);
        $this->_send_client_request();
        
        // test
        if ( ! $this->GreqoResponse->code_class == 2 ||
                empty($this->GreqoResponse->CookieDict[$this->login_key]) )
            $this->err('failed at step 1: AuthCookie');
        
        // payload
        $AuthCookie = $this->GreqoResponse->CookieDict[$this->login_key];
        
        // return
        return $AuthCookie;
    }
    
    function _login_s2($AuthCookie)
    {
        /*
            Login Step 2
            Sends Auth Cookie, login data
            Returns token auth url and sets LoginCookies
        */
        $url = $this->URL['google'].$this->URL['login_auth'];
        $url_cont = $this->URL['google'].$this->URL['login_cont'];
        $method = HTTP_REQUEST_METHOD_POST;
        
        // prepare and send post
        $this->GreqoClient->setURL($url);
        $this->GreqoClient->setMethod($method);
        $this->GreqoClient->addPostData('continue', $url_cont);
        $this->GreqoClient->addPostData('service', 'analytics');
        $this->GreqoClient->addPostData('nui', '1');
        $this->GreqoClient->addPostData('hl', 'en-US');
        $this->GreqoClient->addPostData($AuthCookie['name'], $AuthCookie['value']);
        $this->GreqoClient->addPostData('Email', $this->email);
        $this->GreqoClient->addPostData('Passwd', $this->password);
        $this->_send_client_request();
        
        // test
        if ( ! $this->GreqoResponse->code_class == 2 )
            return $this->err(
                "failed at step 2: Code {$this->GreqoResponse->code_class}");
        
        // payload     
        $tokenauth_url = ereg_replace(".*<a href=\"(.*)\" target=.*", "\\1", $this->GreqoResponse->body);
        $tokenauth_url = ereg_replace('amp;', '', $tokenauth_url);
        $this->LoginCookies = $this->GreqoResponse->CookieDict;
        
        // return
        return $tokenauth_url;
    }
    
    function _login_s3($tokenauth_url)
    {
        /*
            Login Step 3
            Sends cookies to next url 
            Return location url
        */
        $url = $this->absolute_url($this->URL['google'], $tokenauth_url);
        $method = HTTP_REQUEST_METHOD_GET;
        
        // prepare and send request
        $this->GreqoClient->clearPostData();
        foreach ($this->LoginCookies as $name => $Info)
            $this->GreqoClient->addCookie($name, $Info['value']);
        $this->GreqoClient->setURL($url);
        $this->GreqoClient->setMethod($method);
        @$this->_send_client_request();
    
        // test
        if ( ! $this->GreqoResponse->code_class == 3 )
            return $this->err(
                "failed at step 3: expecting redirect code {$this->GreqoResponse->code_class}");
        
        // payload        
        $location_url = $this->GreqoClient->getResponseHeader('Location');
        
        // return
        return $location_url;
    }
    
    function _login_s4($location_url)
    {
        /*
            Login Step 4
            Sends cookies to location url 
            Confirms login is successful and returns status
        */
        // prepare request
        $url = $this->absolute_url($this->URL['google'], $location_url);
        $method = HTTP_REQUEST_METHOD_GET;
        
        // prepare and send request
        $this->GreqoClient->setURL($url);
        $this->GreqoClient->setMethod($method);
        foreach ($this->LoginCookies as $name => $Info)
            $this->GreqoClient->addCookie($name, $Info['value']);
        $this->_send_client_request();
        
        // test
        if ( ! $this->GreqoResponse->code_class == 2 )
            return $this->err(
                "failed at step 4: Code {$this->GreqoResponse->code_class}");
        
        // login successful     
        return 1;
    }
    
    function _parse_account_list($body)
    {
        $AccountList = array();
        $cue = 'name="account_selector"';
        $regex = '%<option.*value="(.*)".*>(.*)</option>%isU';
        
        // test
        if ( strpos($body, $cue) === false )
        return $this->err(
            "account list cue [$cue] not found" );
        
        // crop body and parse accounts
        $body = substr($body, strpos($body, $cue));
        $body = substr($body, 0, strpos($body, "</select>"));
        if ( ! preg_match_all($regex, $body, $Matches) )
            return $this->err('Account List not found');
    
        // create list
        foreach ( $Matches[1] as $n => $account_id )
        {
            $account_name = ( isset($Matches[2][$n]) ) ? $Matches[2][$n] : 0;
            if ( !empty($account_id) && !empty($account_name) )
                $AccountList[$account_name] = $account_id;
        }
        
        return $AccountList;
    }
    
    function _parse_profile_list($body)
    {
        $ProfileList = array();
        $cue = 'account_profile_selector';
        $regex = '%<option.*value="(.*)".*>(.*)</option>%isU';
        
        // test
        if ( strpos($body, $cue) === false )
        return $this->err(
            "profile list cue [$cue] not found" );
        
        // crop body and parse accounts
        $body = substr($body, strpos($body, $cue));
        $body = substr($body, 0, strpos($body, "</select>"));
        if ( ! preg_match_all($regex, $body, $Matches) )
            return $this->err('profile list not found');
    
        // create list
        foreach ( $Matches[1] as $n => $profile_id )
        {
            $profile_name = ( isset($Matches[2][$n]) ) ? $Matches[2][$n] : 0;
            if ( !empty($profile_id) && !empty($profile_name) )
                $ProfileList[$profile_name] = $profile_id;
        }
        
        return $ProfileList;
    }
    
    function _get_cache_id($report_url)
    {
        #return preg_replace('[^A-Za-z0-9]', '', $report_url);
        return sprintf('cache-%s', md5($report_url));
    }
    
    function _report_request_is_ready() {}

    function _get_filename() { return basename(__FILE__); }
    function _get_dirpath() { return dirname(__FILE__) . $this->DS; }

} // end class

?>
